/***************************************************************************
 *   Copyright (C) 2011  by TinySofts                                      *
 *   tinysofts@gmail.com                                                   *
 *   All Rights Reserved.                                                  *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   Refer to http://code.google.com/p/tinydict/                           *
 ***************************************************************************/

/*
    .dz Lib
    Status:Open;ReleaseReady;M
*/

#include <zlib.h>
#include "dictzip.h"

DictZip::DictZip(QString dict) : dictName(dict)
{
    fileHandler.setFileName(dictName);
    fileHandler.open(QFile::ReadOnly);
    _readHeader();
    errorNo = 0;
}

void DictZip::_readHeader()
{
    /*check the magic number*/
    QByteArray magic = fileHandler.read(2);
    if(magic != "\037\213")
    {
        /*Not a gzipped file*/
        errorNo = -1;
        return;
    }
    char method = 0;
    fileHandler.read(&method, 1);
    if(method!=8)
    {
        /*Unknown compression method*/
        errorNo = -2;
        return;
    }
    char flag;
    fileHandler.read(&flag, 1);
    /*skip modtime(4), extraflsg(1), os(1) */
    fileHandler.read(6);
    if(flag & FEXTRA)
    {
        /*Read the extra field*/
        char x;
        int xlen = 0; /*Extra Field Size*/
        fileHandler.read(&x,1);
        xlen = (unsigned char)x;
        fileHandler.read(&x,1);
        xlen = (unsigned char)xlen+256*(unsigned char)x;
        QByteArray data = fileHandler.read(xlen);
        char * extra = data.data();
        char * e = 0;
        while(true)
        {
            int l = (unsigned char)extra[2]+256*(unsigned char)extra[3];
            e = extra;
            if(e[0]!='R'||e[1]!='A')
            {
                e+=4+l;
                if(extra+xlen<e)
                {
                    /*"Missing dictzip extension"*/
                    errorNo = -3;
                    return;
                }
            }
            else
            {
                break;
            }
        }
        //int length = (unsigned char)extra[2]+256*(unsigned char)extra[3];
        //int ver =  (unsigned char)extra[4]+256*(unsigned char)extra[5];
        chunkSize = (unsigned char)extra[6]+256*(unsigned char)extra[7];
        chunkNum = (unsigned char)e[8]+256*(unsigned char)e[9];
        int p = 10;
        QList<int> lens;
        for(int i=0;i<chunkNum;i++)
        {
            int curChLen = (unsigned char)e[p]+256*(unsigned char)e[p+1];
            lens.append(curChLen);
            p+=2;
        }
        int chpos = 0;
        for(int i=0;i<chunkNum;i++)
        {
            int _len = lens[i];
            _Chunk ch;
            ch.chkpos = chpos;
            ch.chklen = _len;
            chunks.append(ch);
            chpos+=_len;
        }

        _lastpos = chpos;
    }
    else
    {
        /*"Missing dictzip extension"*/
        errorNo = -3;
        return;
    }

    if(flag&FNAME)
    {
        /*Read and discard a null-terminated string containing the filename*/
        while (true)
        {
            char c;
            fileHandler.read(&c,1);
            if(c==0)
                break;
        }
    }
    if(flag & FCOMMENT)
    {
        /*Read and discard a null-terminated string containing a comment*/
        while (true)
        {
            char c;
            fileHandler.read(&c,1);
            if(c==0)
                break;
        }
    }
    if(flag & FHCRC)
    {
        /* Read & discard the 16-bit header CRC */
        fileHandler.read(2);
    }
    _firstpos = fileHandler.pos();
}


QByteArray DictZip::_readChunk(int id)
{
    if(id>=chunks.length())
    {
        return 0;
    }
    fileHandler.seek(_firstpos + chunks[id].chkpos);
    QByteArray dat = fileHandler.read(chunks[id].chklen);
    z_stream strm;
    /* initialize file and inflate state to start there */
    unsigned char * outptr = new unsigned char[chunkSize];
    if(outptr==NULL) {
        errorNo = -10;
        return "";
    }
    memset(outptr,0,chunkSize);
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    int r;
    r = inflateInit2(&strm, -15);         /* raw inflate */
    strm.next_in=(unsigned char *)dat.data();
    strm.avail_in=dat.size();
    strm.next_out=outptr;
    strm.avail_out=chunkSize;
    r = inflate(&strm, Z_NO_FLUSH);
    /*MUST*/
    inflateEnd(&strm);
    QByteArray ret((const char*)outptr,chunkSize-strm.avail_out);
    delete outptr;
    return ret;
}

int DictZip::_readChunk(int chunkid, char * buff)
{
    if(chunkid>=chunks.length())
    {
        return 0;
    }
    unsigned char * tempbuff = new unsigned char[chunks[chunkid].chklen];
    if(tempbuff==NULL) {
        errorNo = -10;
        return -10;
    }
    memset(tempbuff,0x00,chunks[chunkid].chklen);
    fileHandler.seek(_firstpos + chunks[chunkid].chkpos);
    int len = fileHandler.read((char*)tempbuff,chunks[chunkid].chklen);
    
    z_stream strm;
    strm.zalloc = Z_NULL;
    strm.zfree = Z_NULL;
    strm.opaque = Z_NULL;
    strm.avail_in = 0;
    strm.next_in = Z_NULL;
    int r;
    r = inflateInit2(&strm, -15); 
    strm.next_in=tempbuff;
    strm.avail_in=len;
    strm.next_out=(unsigned char*)buff;
    strm.avail_out=chunkSize;
    r = inflate(&strm, Z_NO_FLUSH);
    inflateEnd(&strm);
    delete tempbuff;
    return chunkSize-strm.avail_out;
}

QByteArray DictZip::read(int size)
{
    int firstchunk = pos/chunkSize;
    int offset = pos-firstchunk*chunkSize;
    int lastchunk = (pos+size)/chunkSize;
    pos = pos+size;
    QByteArray buff;
    if(size<0)
    {
        size=-1;
        lastchunk = chunks.size()+1;
        pos=-1;
    }
    for(int i=firstchunk;i<=lastchunk;i++)
    {
        buff+=_readChunk(i);
    }
    return buff.mid(offset,size);
}

int DictZip::read(int size, char * ret)
{
    int firstchunk = pos/chunkSize;
    int offset = pos-firstchunk*chunkSize;
    int lastchunk = (pos+size)/chunkSize;
    char * buff = new char[(lastchunk-firstchunk+1)*chunkSize];
    if(buff==NULL) {
        errorNo = -11;
        return -1;
    }
    pos = pos+size;
    if(size<0)
    {
        return 0;
    }
    int k = 0;
    for(int i=firstchunk;i<=lastchunk;i++)
    {
        _readChunk(i,buff+chunkSize*k);
        k++;
    }
    memcpy(ret,buff+offset,size);
    delete buff;
    return size;
    
}

bool DictZip::seek(int offset)
{
    pos = offset;
    return true;
}
